// 3d图像
// Created by denglibin on 2021/4/3 0003.
//
/**
 * OpenGL是一个右手坐标系
 * 为了理解为什么被称为右手坐标系，按如下的步骤做：

沿着正y轴方向伸出你的右臂，手指着上方。
大拇指指向右方。
食指指向上方。
中指向下弯曲90度。
如果你的动作正确，那么你的大拇指指向正x轴方向，食指指向正y轴方向，中指指向正z轴方向。如果你用左臂来做这些动作，你会发现z轴的方向是相反的。
 这个叫做左手坐标系，它被DirectX广泛地使用。注意在标准化设备坐标系中OpenGL实际上使用的是左手坐标系（投影矩阵交换了左右手）
 **/

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "shader.h"
#include "window.h"
#include "gl_object.h"
#include "cglm.h"
// settings
static const  int width = 800;
static const  int height = 600;
//顶点坐标
static float vertices[] = {
        //位置坐标                纹理坐标
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
        0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
};

/**
 * 输入事件监听
 */
static void processInput1(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, 1);
}
/**
 * 设置链接顶点属性
 */
static void setVertexAttr()
{
    /*
    第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗？它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中，所以这里我们传入0。
    第二个参数指定顶点属性的大小。顶点属性是一个vec3，它由3个值组成，所以大小是3。
    第三个参数指定数据的类型，这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
    下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE，所有数据都会被映射到0（对于有符号型signed数据是-1）到1之间。我们把它设置为GL_FALSE。
    第五个参数叫做步长(Stride)，它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个GLfloat之后，我们把步长设置为3 * sizeof(GLfloat)。要注意的是由于我们知道这个数组是紧密排列的（在两个顶点属性之间没有空隙）我们也可以设置为0来让OpenGL决定具体步长是多少（只有当数值是紧密排列时才可用）。一旦我们有更多的顶点属性，我们就必须更小心地定义每个顶点属性之间的间隔，我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
    最后一个参数的类型是GLvoid*，所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头，所以这里是0
    */

    // position attribute 位置坐标
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // texture coord attribute 纹理坐标
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);


}

/**
 * 旋转矩阵
 * @param shaderProgram
 */
static void rotateMat(GLuint shaderProgram){
    mat4 model = GLM_MAT4_IDENTITY_INIT; //定义一个变换矩阵 单位矩阵
    vec3 v = {0.5f, 1.0f, 0.0f};
    glm_rotate(model, (float)glfwGetTime() * 50.0f /180.0f * CGLM_PI, v);

    unsigned int transformLoc = glGetUniformLocation(shaderProgram, "model");
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, (const GLfloat *) model);

}

/**
 * 观察矩阵
 * 观察空间经常被人们称之OpenGL的摄像机(Camera)（所以有时也称为摄像机空间(Camera Space)或视觉空间(Eye Space)）。观察空间是将世界空间坐标转化为用户视野前方的坐标而产生的结果。因此观察空间就是从摄像机的视角所观察到的空间。
 * 而这通常是由一系列的位移和旋转的组合来完成，平移/旋转场景从而使得特定的对象被变换到摄像机的前方。这些组合在一起的变换通常存储在一个观察矩阵(View Matrix)里，它被用来将世界坐标变换到观察空间
 * @param shaderProgram
 */
static void viewMat(GLuint shaderProgram){
    mat4 view = GLM_MAT4_IDENTITY_INIT; //定义一个变换矩阵 单位矩阵
    vec3 v = {0, 0, -3.0f}; //z 轴 垂直屏幕向里，将场景沿着z轴负方向平移来实现。它会给我们一种我们在往后移动的感觉
    glm_translate(view, v); //平移变换

    unsigned int transformLoc = glGetUniformLocation(shaderProgram, "view");
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, (const GLfloat *) view);

}

/**
 * 投影矩阵 使用透视投影
 * @param shaderProgram
 */
static void projectionMat(GLuint shaderProgram){
    mat4 projection = GLM_MAT4_IDENTITY_INIT; //定义一个变换矩阵 单位矩阵
    glm_perspective(45.0f, (float )width/(float )height, 0.1f, 100.0f, projection);
    unsigned int transformLoc = glGetUniformLocation(shaderProgram, "projection");
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, (const GLfloat *) projection);

}
/**
 * 变换矩阵
 * @param shaderProgram
 */
static void transformMat(GLuint shaderProgram){
    rotateMat(shaderProgram);
    viewMat(shaderProgram);
    projectionMat(shaderProgram);
}



int main11(void)
{
    //初始化，创建窗口
    GLFWwindow *window = initWindow(width, height, "LearnOpenGL");

    /**
     * OpenGL存储它的所有深度信息于一个Z缓冲(Z-buffer)中，也被称为深度缓冲(Depth Buffer)。GLFW会自动为你生成这样一个缓冲（就像它也有一个颜色缓冲来存储输出图像的颜色）。深度值存储在每个片段里面（作为片段的z值），当片段想要输出它的颜色时，OpenGL会将它的深度值和z缓冲进行比较，如果当前的片段在其它片段之后，它将会被丢弃，否则将会覆盖。这个过程称为深度测试(Depth Testing)，它是由OpenGL自动完成的
     **/
    //开启深度测试
    glEnable(GL_DEPTH_TEST);
    //创建着色器程序
    //编译着色器源码，创建着色器程序
    GLuint shaderProgram = compileShaderSource("D:\\CLionProjects\\opengl-mingw\\shader\\shader11.vs",
                                               "D:\\CLionProjects\\opengl-mingw\\shader\\shader11.fs");

    GLuint VBO = createVBO(vertices, sizeof (vertices) / sizeof (float ));
    GLuint VAO = createVAO();
    setVertexAttr();
    //创建纹理 第一个

    GLuint texture1 = createTexture("D:\\CLionProjects\\opengl-mingw\\img\\container.jpg", GL_RGB, GL_RGB);
    //创建纹理 第二个
    GLuint texture2 = createTexture("D:\\CLionProjects\\opengl-mingw\\img\\awesomeface.png", GL_RGBA, GL_RGBA);



    //在设置uniform变量之前，激活着色器程序
    glUseProgram(shaderProgram); // don't forget to activate/use the shader before setting uniforms!
    //使用glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元
    //只需要设置一次即可，所以这个会放在渲染循环的前面

    glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0);
    glUniform1i(glGetUniformLocation(shaderProgram, "texture2"), 1);



    //一般当你打算绘制多个物体时，你首先要生成/配置所有的VAO（和必须的VBO及属性指针)，然后储存它们供后面使用。当我们打算绘制物体的时候就拿出相应的VAO，绑定它，绘制完物体后，再解绑VAO。
    while (!glfwWindowShouldClose(window))
    {
        processInput1(window);
        //设置清除颜色
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        //使用上面设置的颜色清空屏幕
       // glClear(GL_COLOR_BUFFER_BIT);
        //因为使用了深度测试，我们也想要在每次渲染迭代之前清除深度缓冲（否则前一帧的深度信息仍然保存在缓冲中）。就像清除颜色缓冲一样
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        //绑定纹理第一个
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1);
        //绑定纹理第二个
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);


        //调用glUseProgram函数，用刚创建的程序对象作为它的参数，以激活这个程序对象
        glUseProgram(shaderProgram);

        transformMat(shaderProgram);
        //绑定VAO
        glBindVertexArray(VAO);

        glDrawArrays(GL_TRIANGLES, 0, 36);

        //交换颜色缓冲区
        glfwSwapBuffers(window);
        //检查事件
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    //glfwTerminate会销毁窗口释放资源，因此在调用该函数后，如果想使用glfw库函数，就必须重新初始化。
    glfwTerminate();
    return 0;
}